﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
using Microsoft.Practices.Unity;
using Wyn.PMS.Common.Utils;
using Wyn.PMS.Entity.DataAccess;
using Wyn.PMS.Entity.Entities;
using Wyn.PMS.Entity.IDataAccess;

namespace Wyn.PMS.Common.NightAudit
{
    internal class OperationStatus
    {
        private static readonly IUnityContainer container = UnityContainerService.GetContainer();

        internal IList<NaOperationStatus> Snapshot(HotelBizDay bizDay, IList<RoomType> roomTypes, IList<Account> notRollAccounts, IDictionary<string, decimal> rollRoomPrice, IDictionary<string, decimal> rtRoomFeeAccounts,IList<Order> rollAccountOrders, IList<Order> allOrders)
        {
            INaOperationStatusRepository statusRepository = container.Resolve<INaOperationStatusRepository>();

            IRoomOccupyRepository roomOccupyRepository = container.Resolve<IRoomOccupyRepository>();

            IReserveRepository resvRepository = container.Resolve<IReserveRepository>();

            IOrderRepository orderRepository = container.Resolve<IOrderRepository>();

            var transactionOptions = new System.Transactions.TransactionOptions();

            transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;
            
            string hotelId = bizDay.HotelID;

            var rsMonitor = CacheMonitor.GetInstance();

            var rooms = rsMonitor.GetCacheRooms(GlobalConst.DEFAULT_START_TIME, hotelId).Value;

            DateTime todayBizDay = bizDay.BizDay;

            DateTime todayBizMax = todayBizDay.AddDays(1).AddSeconds(-1);

            IDictionary<string, Expression<Func<RoomOccupy, bool>>> exps = BuildWillInRoomTypeCountExp(todayBizDay, roomTypes);
            
            IDictionary<string, int> willInRoomTypeCounts = roomOccupyRepository.CountRoomTypeOccupies(hotelId, exps);

            string resvStatusX = EnumHelper.GetCode(ReserveStatus.X);

            string resvStatusR = EnumHelper.GetCode(ReserveStatus.R);

            string resvStatusN = EnumHelper.GetCode(ReserveStatus.N);

            int orderTypeR = (int)OrderType.Reserve;
            
            Expression<Func<RoomOccupy, bool>> resvExp =
                r => r.HotelID == hotelId
                     && r.OrderType == orderTypeR
                     && r.BookBizDay == todayBizDay
                     && (r.IsValid || (!r.IsValid && (r.IsCancel || r.IsNoShow)));
         
            Expression<Func<RoomOccupy, bool>> cancelExp =
                r => r.HotelID == hotelId
                     && r.CancelBizDay == todayBizDay
                     && r.OrderType == orderTypeR
                     && r.IsCancel;

            Expression<Func<Reservation, bool>> nowShowExp =
                r => r.HotelID == hotelId
                     &&( ( r.StartDate < todayBizMax && r.Status == resvStatusR && r.IsGuarantee == false)
                     ||r.ModifiedTime>=todayBizDay && r.Status==resvStatusN);
           
            IDictionary<string, Expression<Func<RoomOccupy, bool>>> dayOccupyExps = BuildDayOccupyCountExp(todayBizDay, roomTypes);

            IList<RoomOccupy> resvOccupies = new List<RoomOccupy>();

            IList<RoomOccupy> todayCancelOccupies = new List<RoomOccupy>();

            IList<Reservation> noShowResvation = new List<Reservation>();
            
            IDictionary<string, int> dayOccupyCounts = new Dictionary<string, int>();
            
            using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required, transactionOptions))
            {
                resvOccupies = roomOccupyRepository.BatchFind(hotelId, resvExp);

                todayCancelOccupies = roomOccupyRepository.BatchFind(hotelId, cancelExp);

                dayOccupyCounts = roomOccupyRepository.CountRoomTypeOccupies(hotelId, dayOccupyExps);

                noShowResvation = resvRepository.BatchIncludeFind(hotelId, nowShowExp);
                
                scope.Complete();
            }
          
            IList<RoomOccupy> noShowOccupies = BuildNoShowOccupies(noShowResvation);
           
            IList<NaOperationStatus> statuses = new List<NaOperationStatus>();

            IList<RollAccountOrderCount> rollAccountOrderCounts = CountRollAccountOrder(notRollAccounts, rollAccountOrders, roomTypes);
            
            IDictionary<string, decimal> averageRoomPrice = BuildAverageRoomPrice(rtRoomFeeAccounts, notRollAccounts, rollAccountOrders, roomTypes, rollAccountOrderCounts, bizDay);
            
            foreach (var roomType in roomTypes)
            {
                var roomTypeRooms = rooms.Where(r => r.RoomTypeID == roomType.RoomTypeID).ToList();

                IEnumerable<RoomOccupy> roomTypeResvOccupies = resvOccupies.Where(o => o.RoomTypeID == roomType.RoomTypeID);

                int roomCount = dayOccupyCounts.FirstOrDefault(r => r.Key == roomType.RoomTypeID).Value;

                var roomTypeOrders = allOrders.Where(o => o.RoomTypeID == roomType.RoomTypeID).ToList();

                var roomTypeAccountOrderCount = rollAccountOrderCounts.FirstOrDefault(o => o.Name == roomType.RoomTypeID);

                NaOperationStatus status = BuildStatus(bizDay, roomType, roomTypeOrders, roomTypeAccountOrderCount, roomTypeRooms, roomTypeResvOccupies, roomCount);
                
                status.NoShow = noShowOccupies.Count(r => r.RoomTypeID == roomType.RoomTypeID);

                status.ResvCancel = todayCancelOccupies.Count(r => r.RoomTypeID == roomType.RoomTypeID);

                status.WillArriveCount = willInRoomTypeCounts.FirstOrDefault(r => r.Key == roomType.RoomTypeID).Value;

                if (rollRoomPrice != null)
                {
                    status.AuditRoomPrice = rollRoomPrice[status.RoomTypeID];
                }

                status.TodayRoomReceipts = rtRoomFeeAccounts.FirstOrDefault(r => r.Key == status.RoomTypeID).Value;
                
                status.RoomTypePrice = averageRoomPrice.FirstOrDefault(r => r.Key == roomType.RoomTypeID).Value;

                statuses.Add(status);
            }
            
            var noShowOrders = UpdateStatusAndGetOrders(noShowResvation, noShowOccupies, bizDay);

            if (bizDay.Status != EnumHelper.GetCode(NightAuditCode.Status.E)
                || (bizDay.Status == EnumHelper.GetCode(NightAuditCode.Status.E)
                && bizDay.Steping == (int)NightAuditCode.Steping.OperationStatus))
            {
                bizDay.Status = EnumHelper.GetCode(NightAuditCode.Status.I);

                using (TransactionScope scope = new TransactionScope())
                {
                    statusRepository.BatchInsert(hotelId, statuses);
                    
                    resvRepository.BatchUpdate(hotelId, noShowResvation);
                    
                    roomOccupyRepository.BatchUpdate(hotelId, noShowOccupies);

                    orderRepository.BatchUpdate(hotelId, noShowOrders);

                    scope.Complete();
                }
            }

            return statuses;
        }

        private NaOperationStatus BuildStatus(HotelBizDay bizDay, RoomType roomType, IList<Order> orders, RollAccountOrderCount rollOrderCount, IList<Room> rooms, IEnumerable<RoomOccupy> resvOccupies, int occupyCount)
        {
            NaOperationStatus status = new NaOperationStatus();

            status.HotelID = bizDay.HotelID;

            status.BizDay = bizDay.BizDay;

            status.RoomTypeID = roomType.RoomTypeID;

            status.RoomTypeName = roomType.RoomTypeName;

            status.Count = roomType.TotalRooms;

            status.OccupiesCount = occupyCount;

            var allOOO = rooms.Where(r => r.Status == EnumHelper.GetCode(RoomStatus.OOO));

            if (allOOO.Any())
            {
                status.OOOCount = allOOO.Count(r =>
                {
                    var firstOrDefault = r.RoomAttribute.FirstOrDefault(a => a.AttributeCode == EnumHelper.GetCode(RoomAttributeCode.RP));
                    return firstOrDefault == null || firstOrDefault.IsValid == false;
                });

                status.RepairCount = allOOO.Count() - status.OOOCount;
            }

            status.LockCount = rooms.Count(r =>
            {
                var roomAttribute = r.RoomAttribute.FirstOrDefault(a =>
                    a.AttributeCode == EnumHelper.GetCode(RoomAttributeCode.LR));
                return roomAttribute != null && roomAttribute.IsValid;
            });

            status.VDCount = rooms.Count(r => r.Status == EnumHelper.GetCode(RoomStatus.VD));

            status.VCCount = rooms.Count(r => r.Status == EnumHelper.GetCode(RoomStatus.VC));

            status.ODCount = rooms.Count(r => r.Status == EnumHelper.GetCode(RoomStatus.OD));

            status.OCCount = rooms.Count(r => r.Status == EnumHelper.GetCode(RoomStatus.OC));
        
            status.CheckOutCount =  orders.Count(a => a.CheckOutBizDay.HasValue && a.CheckOutBizDay.Value == bizDay.BizDay);
            
            status.EarlyCheckOut = orders.Count(a => a.CheckOutTime.HasValue
                                                           && a.CheckOutTime.Value.Date < a.DepartureDate.Date
                                                           && a.CheckOutBizDay.Value == bizDay.BizDay);

            status.CheckInAndOut = orders.Count(a => a.CheckOutBizDay.HasValue && a.CheckInBizDay.Value == a.CheckOutBizDay.Value && a.CheckOutBizDay.Value == bizDay.BizDay);

            status.CheckInCount = orders.Count(a => a.CheckInBizDay.HasValue && a.CheckInBizDay.Value == bizDay.BizDay);

            DateTime tomorrow = bizDay.BizDay.AddDays(2).AddSeconds(-1);

            status.WillLeaveCount = orders.Count(a => a.Status == EnumHelper.GetCode(OrderStatus.I) && a.DepartureDate <= tomorrow);

            status.ResvArrived = orders.Count(a => a.CheckInBizDay == bizDay.BizDay && !string.IsNullOrEmpty(a.ResvID));

            status.TodayLeave = orders.Count(a => a.CheckOutBizDay.HasValue && a.CheckOutBizDay.Value == bizDay.BizDay);

            //TODO:看要不要减去noshow量
            status.ResvCount = resvOccupies.Count();

            status.ResvAssigned = resvOccupies.Count(o => !string.IsNullOrEmpty(o.RoomNo));

            //TODO:看是统计今日预订的量还是统计所有有效预订的量
            status.ResvRoomCount = CountPassNight(resvOccupies);

            status.HourRoomCount = orders.Count(a =>a.CheckInType == EnumHelper.GetCode(CheckInType.HR));

            status.DayRoomCount = orders.Count(a => a.CheckInType == EnumHelper.GetCode(CheckInType.DR));

            status.FreeRoomCount = orders.Count(a => a.CheckInType == EnumHelper.GetCode(CheckInType.FR));

            status.SelfRoomCount = orders.Count(a =>a.CheckInType == EnumHelper.GetCode(CheckInType.SU));

            status.AgentRoomCount = orders.Count(a =>!string.IsNullOrWhiteSpace(a.MemberNo));

            status.ExtraHalfDayPriceCount = rollOrderCount.ExtraHalfDay;

            status.ExtraPassNightCount = rollOrderCount.ExtraFullDay;
            
            int wait = (roomType.TotalRooms - occupyCount) < 0 ? Math.Abs(roomType.TotalRooms - occupyCount) : 0;

            status.ResvWait = wait;

            return status;
        }
        
        private IDictionary<string, Expression<Func<RoomOccupy, bool>>> BuildWillInRoomTypeCountExp(DateTime bizDay,IList<RoomType> roomTypes)
        {
            IDictionary<string, Expression<Func<RoomOccupy, bool>>> exps =
                new Dictionary<string, Expression<Func<RoomOccupy, bool>>>();

            DateTime tomorrow = bizDay.AddDays(1);

            DateTime tomorrowMax = bizDay.AddDays(2).AddSeconds(-1);

            foreach(var type in roomTypes)
            {
                string roomTypeId = type.RoomTypeID;

                string hotelId = type.HotelID;

                int orderTypeR = (int) OrderType.Reserve;

                Expression<Func<RoomOccupy, bool>> exp = r => r.HotelID == hotelId
                                                              && r.RoomTypeID == roomTypeId
                                                              && r.OrderType == orderTypeR
                                                              && r.StartDate >= tomorrow
                                                              && r.StartDate <= tomorrowMax
                                                              && r.IsValid;

                exps.Add(roomTypeId, exp);
            }

            return exps;
        }

        private IDictionary<string, Expression<Func<RoomOccupy, bool>>> BuildDayOccupyCountExp(DateTime bizDay, IList<RoomType> roomTypes)
        {
            IDictionary<string, Expression<Func<RoomOccupy, bool>>> exps = new Dictionary<string, Expression<Func<RoomOccupy, bool>>>();

            DateTime bizDayMax = bizDay.AddDays(1).AddSeconds(-1);

            foreach (var type in roomTypes)
            {
                string roomTypeId = type.RoomTypeID;

                string hotelId = type.HotelID;

                Expression<Func<RoomOccupy, bool>> exp = r => r.HotelID == hotelId
                                                              && r.RoomTypeID == roomTypeId
                                                              && r.StartDate <= bizDayMax
                                                              && r.IsValid;

                exps.Add(roomTypeId, exp);
            }

            return exps;
        }

        private IList<Order> BuildNoShowOrders(IList<Reservation> noShowResvation)
        {
            IEnumerable<Order> orders = new List<Order>();

            foreach (var resv in noShowResvation)
            {
                var noshow = resv.Order.Where(o => o.Status == EnumHelper.GetCode(OrderStatus.R));

                if(noshow.Any())
                {
                    orders = orders.Union(noshow);
                }
            }

            return orders.ToList();
        }

        private IList<RoomOccupy> BuildNoShowOccupies(IList<Reservation> noShowResvation)
        {
            IEnumerable<RoomOccupy> occupies = new List<RoomOccupy>();

            foreach (var resv in noShowResvation)
            {
                var noshow = resv.RoomOccupy.Where(o => o.OrderType ==(int)OrderType.Reserve && o.IsValid);

                if (noshow.Any())
                {
                    occupies = occupies.Union(noshow);
                }
            }

            return occupies.ToList();
        }
        
        private int CountPassNight(IEnumerable<RoomOccupy> resvOccupies)
        {
            int count = 0;

            foreach (var occupy in resvOccupies)
            {
                count += (occupy.EndDate - occupy.StartDate).Days;
            }

            return count;
        }

        private IDictionary<string, decimal> BuildAverageRoomPrice(IDictionary<string, decimal> rtRoomFeeAccounts, IList<Account> notRollAccounts, IList<Order> rollAccountOrders, IList<RoomType> roomTypes, IList<RollAccountOrderCount> rollAccountOrderCounts, HotelBizDay bizDay)
        {
            var fp = FormulaProvider.Instance;

            var formula= fp.GetFormula(FormulaProvider.AvgRoomPrice);

            IDictionary<string, decimal> prices = new Dictionary<string, decimal>();

            foreach (var roomtype in roomTypes)
            {
                var count= rollAccountOrderCounts.FirstOrDefault(c => c.Name == roomtype.RoomTypeID);
                
                decimal rtFeeSum = rtRoomFeeAccounts[roomtype.RoomTypeID];

                decimal price = 0;

                if (count != null)
                {
                    decimal rtCount = (decimal) (count.CheckInType_NS/formula.CheckInType_NS
                                                 + count.CheckInType_DR/formula.CheckInType_DR
                                                 + count.CheckInType_HR/formula.CheckInType_HR
                                                 + count.CheckInType_FR/formula.CheckInType_FR
                                                 + count.CheckInType_SU/formula.CheckInType_SU
                                                 + count.ExtraHalfDay/formula.Ledger_ExtraHalfDay
                                                 + count.ExtraFullDay/formula.Ledger_ExtraFullDay);

                    if(rtCount>0)
                    {
                        price = Math.Round(rtFeeSum / rtCount, 2);
                    }
                }
                prices.Add(roomtype.RoomTypeID, price);
            }

            return prices;
        }

        private IList<RollAccountOrderCount> CountRollAccountOrder(IList<Account> notRollAccounts, IList<Order> rollAccountOrders, IList<RoomType> roomTypes)
        {
            IList<RollAccountOrderCount> racs = new List<RollAccountOrderCount>();

            foreach (var roomtype in roomTypes)
            {
                RollAccountOrderCount rac = new RollAccountOrderCount();

                rac.Name = roomtype.RoomTypeID;

                rac.CheckInType_NS = rollAccountOrders.Count(o => o.CheckInType == EnumHelper.GetCode(CheckInType.NS) && o.RoomTypeID == roomtype.RoomTypeID);

                rac.CheckInType_DR = rollAccountOrders.Count(o => o.CheckInType == EnumHelper.GetCode(CheckInType.DR) && o.RoomTypeID == roomtype.RoomTypeID);

                rac.CheckInType_HR = rollAccountOrders.Count(o => o.CheckInType == EnumHelper.GetCode(CheckInType.HR) && o.RoomTypeID == roomtype.RoomTypeID);

                rac.CheckInType_FR = rollAccountOrders.Count(o => o.CheckInType == EnumHelper.GetCode(CheckInType.FR) && o.RoomTypeID == roomtype.RoomTypeID);

                rac.CheckInType_SU = rollAccountOrders.Count(o => o.CheckInType == EnumHelper.GetCode(CheckInType.SU) && o.RoomTypeID == roomtype.RoomTypeID);

                var rtOderIds = rollAccountOrders.Where(o => o.RoomTypeID == roomtype.RoomTypeID).Select(o => o.OrderID);

                rac.ExtraHalfDay = notRollAccounts.Count(a => rtOderIds.Contains(a.AccountRootID) && a.LedgerCode == LedgerCode.半日租);

                rac.ExtraFullDay = notRollAccounts.Count(a => rtOderIds.Contains(a.AccountRootID) && a.LedgerCode == LedgerCode.全日租);

                racs.Add(rac);
            }

            return racs;
        }

        

        private IEnumerable<Order> UpdateStatusAndGetOrders(IList<Reservation> noShowResvation, IList<RoomOccupy> noShowOccupies, HotelBizDay bizDay)
        {
            IEnumerable<Order> noshowOrders = BuildNoShowOrders(noShowResvation);

            foreach(var order in noshowOrders)
            {
                order.Status = EnumHelper.GetCode(OrderStatus.X);
                order.ModifiedTime = DateTime.Now;
                order.EmployeeID = GlobalConst.NIGHT_AUDIT_SYSTEM;
            }

            foreach(var occupy in noShowOccupies)
            {
                occupy.IsNoShow = true;
                occupy.IsValid = false;
                occupy.CancelBizDay = bizDay.BizDay;
                occupy.ModifiedTime = DateTime.Now;
                occupy.EmployeeID = GlobalConst.NIGHT_AUDIT_SYSTEM;
            }

            foreach(var resv in noShowResvation)
            {
                resv.Status = EnumHelper.GetCode(ReserveStatus.N);
            }
            return noshowOrders;
        }
        
    }

    public class RollAccountOrderCount
    {
        public string Name { get;set;}

        public int CheckInType_NS { get; set; }

        public int CheckInType_DR { get; set; }

        public int CheckInType_HR { get; set; }

        public int CheckInType_FR { get; set; }

        public int CheckInType_SU { get; set; }

        public int ExtraHalfDay { get; set; }

        public int ExtraFullDay { get; set; }

    }

}
